commonlibsse_ng\rel\id\id_database/
mod.rs

1// C++ Original code
2// - https://github.com/SARDONYX-forks/CommonLibVR/blob/ng/include/REL/ID.h
3// - https://github.com/SARDONYX-forks/CommonLibVR/blob/ng/src/REL/ID.cpp
4// SPDX-FileCopyrightText: (C) 2018 Ryan-rsm-McKenzie
5// SPDX-License-Identifier: MIT
6
7//! This module provides functionality for loading and managing an ID-to-offset mapping
8//! from a binary address library. It is primarily used to resolve function or data
9//! addresses based on their ID values.
10//!
11//! The ID database is loaded from a precompiled binary file that is versioned based
12//! on the runtime environment and module state. This ensures compatibility between
13//! different versions of the script extender and the game runtime.
14
15mod bin_loader;
16mod byte_reader;
17mod header;
18mod unpack;
19
20use super::Mapping;
21use crate::rel::version::Version;
22use shared_rwlock::SharedRwLock;
23use std::{num::NonZeroUsize, path::PathBuf, sync::LazyLock};
24
25/// Global static instance of `IdDatabase` initialized lazily.
26/// This ensures the database is only loaded when needed.
27pub(crate) static ID_DATABASE: LazyLock<IDDatabase> = LazyLock::new(|| {
28    // TODO: remove unwrap
29    #[cfg(not(any(feature = "test_on_ci", feature = "test_on_local")))]
30    return IDDatabase::from_bin().unwrap_or_else(|err| {
31        #[cfg(feature = "tracing")]
32        tracing::error!("[Critical Error] Failed to load the ID database: {err}");
33        panic!("[Critical Error] Failed to load the ID database: {err}");
34    });
35    #[cfg(any(feature = "test_on_ci", feature = "test_on_local"))]
36    return IDDatabase::from_bin().unwrap_or_else(|_| IDDatabase::new_dummy());
37});
38
39/// Represents a database of ID-to-offset mappings loaded from an address library binary file.
40#[derive(Debug)]
41pub struct IDDatabase {
42    /// Memory-mapped storage of the ID database.
43    pub(crate) mem_map: SharedRwLock<Mapping>,
44}
45
46impl IDDatabase {
47    #[cfg(any(feature = "test_on_ci", feature = "test_on_local"))]
48    fn new_dummy() -> Self {
49        use windows::core::h;
50
51        let (mem_map, _is_created) = SharedRwLock::new(h!("Dummy for test"), 1).unwrap();
52        Self { mem_map }
53    }
54
55    /// Loads the ID database from the appropriate binary file based on the module state.
56    ///
57    /// # Errors
58    /// Returns an error if the module state is invalid, the file cannot be read,
59    /// or if the data is not properly formatted.
60    fn from_bin() -> Result<Self, DataBaseError> {
61        use self::bin_loader::load_bin_file;
62        use crate::rel::module::ModuleState;
63
64        let (version, runtime) = ModuleState::map_or_init(|module| {
65            let version = module.version.clone();
66            (version, module.runtime)
67        })?;
68
69        let is_ae = runtime.is_ae();
70        let path = {
71            let ver_suffix = if is_ae { "lib" } else { "" };
72            let version = version.to_address_library_string();
73            #[cfg(feature = "test_on_local")]
74            {
75                let skyrim_dir = crate::rel::module::get_skyrim_dir(
76                    crate::rel::module::Runtime::Se,
77                )
78                .ok_or(DataBaseError::AddressLibraryNotFound {
79                    path: std::path::Path::new(&version).to_path_buf(),
80                })?;
81                let skyrim_dir = skyrim_dir.display();
82                format!("{skyrim_dir}/Data/SKSE/Plugins/version{ver_suffix}-{version}.bin")
83            }
84            #[cfg(not(feature = "test_on_local"))]
85            {
86                format!("Data/SKSE/Plugins/version{ver_suffix}-{version}.bin")
87            }
88        };
89        let expected_fmt_ver = if is_ae { 2 } else { 1 }; // Expected AddressLibrary format version. SE/VR: 1, AE: 2
90
91        Ok(Self { mem_map: load_bin_file(&path, version, expected_fmt_ver)? })
92    }
93
94    /// Gets the offset corresponding to the given ID.
95    ///
96    /// - Order: `O(log n)` for binary search
97    ///
98    /// # Errors
99    /// Returns an error under the following conditions.
100    /// - If the offset corresponding to the ID is not found.
101    /// - If the offset corresponding to the ID is found, but it is 0.
102    pub(crate) fn id_to_offset(&self, id: u64) -> Result<NonZeroUsize, DataBaseError> {
103        let slice = self.mem_map.read().map_err(|_| DataBaseError::MappingCreationFailed)?;
104
105        slice.binary_search_by(|m| m.id.cmp(&id)).map_or(
106            Err(DataBaseError::NotFoundId { id }),
107            |index| {
108                let offset = slice[index].offset as usize;
109                drop(slice);
110                NonZeroUsize::new(offset).ok_or(DataBaseError::ZeroOffset { id })
111            },
112        )
113    }
114}
115
116/// Errors that can occur during the file loading process.
117#[derive(Debug, Clone, snafu::Snafu)]
118pub enum DataBaseError {
119    /// Could not find this ID({id}) in AddressLibrary. Possible reasons are: wrong ID specification, This plugin is incompatible, etc.
120    NotFoundId { id: u64 },
121
122    /// The offset for this ID({id}) in AddressLibrary was 0. That's an invalid offset.
123    ZeroOffset { id: u64 },
124
125    /// For an offset for which no ID is provided, 0 is specified, which is an invalid offset.
126    SpecifiedZeroOffset,
127
128    /// Version mismatch
129    #[snafu(display("Version mismatch: expected {}, got {}", expected, actual))]
130    VersionMismatch { expected: Version, actual: Version },
131
132    /// Failed to create shared mapping
133    MappingCreationFailed,
134
135    /// Failed to locate an appropriate address library.
136    #[snafu(display("Failed to locate an appropriate address library at: {}", path.display()))]
137    AddressLibraryNotFound { path: PathBuf },
138
139    /// Failed to unpack file at: {source}
140    FailedUnpackFile { source: self::unpack::UnpackError },
141
142    /// Inherited module state(manager) get error.
143    #[snafu(transparent)]
144    ModuleStateError { source: crate::rel::module::ModuleStateError },
145
146    /// Inherited header parsing error.
147    #[snafu(transparent)]
148    HeaderParseError { source: self::header::HeaderError },
149
150    /// A thread that was taking database locks panicked.
151    Poisoned,
152
153    /// Inherited memory mapping error.
154    #[snafu(transparent)]
155    MemoryMapError { source: shared_rwlock::LockError },
156}